package middleware

import (
	"bytes"
	"encoding/json"
	"io"
	"io/ioutil"
	"os"
	"strconv"
	"strings"
	"sync"
	"oxy_server/model"
	"oxy_server/module/log"
	"time"

	"github.com/dgrijalva/jwt-go"
	"github.com/labstack/echo"
	"github.com/labstack/gommon/color"
	"github.com/valyala/fasttemplate"
)

type (
	// LoggerConfig defines the config for Logger middleware.
	LoggerConfig struct {
		// Skipper defines a function to skip middleware.
		Skipper Skipper

		// Tags to constructed the logger format.
		//
		// - time_unix
		// - time_unix_nano
		// - time_rfc3339
		// - time_rfc3339_nano
		// - time_custom
		// - id (Request ID)
		// - remote_ip
		// - uri
		// - host
		// - method
		// - path
		// - protocol
		// - referer
		// - user_agent
		// - status
		// - error
		// - latency (In nanoseconds)
		// - latency_human (Human readable)
		// - bytes_in (Bytes received)
		// - bytes_out (Bytes sent)
		// - header:<NAME>
		// - query:<NAME>
		// - form:<NAME>
		//
		// Example "${remote_ip} ${status}"
		//
		// Optional. Default value DefaultLoggerConfig.Format.
		Format string `yaml:"format"`

		// Optional. Default value DefaultLoggerConfig.CustomTimeFormat.
		CustomTimeFormat string `yaml:"custom_time_format"`

		// Output is a writer where logs in JSON format are written.
		// Optional. Default value os.Stdout.
		Output io.Writer

		template *fasttemplate.Template
		colorer  *color.Color
		pool     *sync.Pool
	}

	insertLogger struct {
		User       string `json:"user" bson:"user"`
		Role       string `json:"role" bson:"role"`
		RemoteIP   string `json:"remoteip" bson:"remoteip"`
		Time       string `json:"time" bson:"time"`
		Method     string `json:"method" bson:"method"`
		URI        string `json:"uri" bson:"uri"`
		Status     string `json:"status" bson:"status"`
		Body       string `json:"body" bson:"body"`
		Beforebody string `json:"beforebody" bson:"beforebody"`
	}
)

var (
	// DefaultLoggerConfig is the default Logger middleware config.
	DefaultLoggerConfig = LoggerConfig{
		Skipper: DefaultSkipper,
		Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}",` +
			`"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` +
			`"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"` +
			`,"bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n",
		CustomTimeFormat: "2006-01-02 15:04:05.00000",
		Output:           os.Stdout,
		colorer:          color.New(),
	}
)

// Logger returns a middleware that logs HTTP requests.
func Logger() echo.MiddlewareFunc {
	return LoggerWithConfig(DefaultLoggerConfig)
}

// LoggerWithConfig returns a Logger middleware with config.
// See: `Logger()`.
func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
	// Defaults
	if config.Skipper == nil {
		config.Skipper = DefaultLoggerConfig.Skipper
	}
	if config.Format == "" {
		config.Format = DefaultLoggerConfig.Format
	}
	if config.Output == nil {
		config.Output = DefaultLoggerConfig.Output
	}

	config.template = fasttemplate.New(config.Format, "${", "}")
	config.pool = &sync.Pool{
		New: func() interface{} {
			return bytes.NewBuffer(make([]byte, 256))
		},
	}

	return func(next echo.HandlerFunc) echo.HandlerFunc {
		// config.Output, _ = CreateFile()

		return func(c echo.Context) (err error) {
			if config.Skipper(c) {
				return next(c)
			}

			req := c.Request()
			res := c.Response()
			body := req.Body
			s, err := ioutil.ReadAll(body)
			ss := make(map[string]interface{})
			err = json.Unmarshal(s, &ss)
			if err != nil {
			}
			bufbody := bytes.NewBuffer(s)
			req.Body = ioutil.NopCloser(bufbody)

			start := time.Now()
			if c == nil {
				log.Info("============c nil")
			}
			if err = next(c); err != nil {
				c.Error(err)
			}
			stop := time.Now()
			buf := config.pool.Get().(*bytes.Buffer)
			buf.Reset()
			defer config.pool.Put(buf)

			m := insertLogger{}
			if _, err = config.template.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) {
				switch tag {
				case "user":
					token, err := AuthToken(c)
					if err != nil {
						return 0, nil
					}
					claims := token.Claims.(jwt.MapClaims)
					name := claims["name"].(string)
					m.User = name
					return 0, nil
				case "role":
					token, err := AuthToken(c)
					if err != nil {
						return 0, nil
					}
					claims := token.Claims.(jwt.MapClaims)
					role := claims["role"].(string)
					m.Role = role
					return 0, nil
				// case "time_unix":
				// 	m["time_unix"] = strconv.FormatInt(time.Now().Unix(), 10)
				// 	return 0, nil
				// case "time_unix_nano":
				// 	m["time_unix_nano"] = strconv.FormatInt(time.Now().UnixNano(), 10)
				// case "time_rfc3339":
				// 	m["time_rfc3339"] = time.Now().Format(time.RFC3339)
				// 	return 0, nil
				case "time_rfc3339_nano":
					m.Time = time.Now().Format(time.RFC3339Nano)
					return buf.WriteString(time.Now().Format(time.RFC3339Nano))
				// case "time_custom":
				// 	m["time_custom"] = time.Now().Format(config.CustomTimeFormat)
				// 	return 0, nil
				// case "id":
				// 	id := req.Header.Get(echo.HeaderXRequestID)
				// 	if id == "" {
				// 		id = res.Header().Get(echo.HeaderXRequestID)
				// 	}
				// 	m["id"] = id
				// 	return 0, nil
				case "remote_ip":
					m.RemoteIP = c.RealIP()
					return 0, nil
				// case "host":
				// 	m["host"] = req.Host
				// 	return 0, nil
				case "body":
					insertbody, err := json.Marshal(ss)
					if err != nil {
						return 0, nil
					}
					m.Body = string(insertbody)
					return 0, nil
				case "beforebody":
					beforebody, err := ioutil.ReadAll(req.Body)
					if err != nil {
						return 0, nil
					}
					// beforebodymap := make(map[string]interface{})
					// err = json.Unmarshal(beforebody, &beforebodymap)
					// if err != nil {
					// 	return 0, nil
					// }
					m.Beforebody = string(beforebody)
					return 0, nil
				case "uri":
					m.URI = req.RequestURI
					return 0, nil
				case "method":
					m.Method = req.Method
					return 0, nil
				case "path":
					p := req.URL.Path
					if p == "" {
						p = "/"
					}
					return buf.WriteString(p)
				case "protocol":
					return buf.WriteString(req.Proto)
				case "referer":
					return buf.WriteString(req.Referer())
				case "user_agent":
					return buf.WriteString(req.UserAgent())
				case "status":
					n := strconv.Itoa(res.Status)
					m.Status = n
					return 0, nil
				case "error":
					if err != nil {
						return buf.WriteString(err.Error())
					}
				case "latency":
					l := stop.Sub(start)
					return buf.WriteString(strconv.FormatInt(int64(l), 10))
				case "latency_human":
					return buf.WriteString(stop.Sub(start).String())
				case "bytes_in":
					cl := req.Header.Get(echo.HeaderContentLength)
					if cl == "" {
						cl = "0"
					}
					return buf.WriteString(cl)
				case "bytes_out":
					return buf.WriteString(strconv.FormatInt(res.Size, 10))
				default:
					switch {
					case strings.HasPrefix(tag, "header:"):
						return buf.Write([]byte(c.Request().Header.Get(tag[7:])))
					case strings.HasPrefix(tag, "query:"):
						return buf.Write([]byte(c.QueryParam(tag[6:])))
					case strings.HasPrefix(tag, "form:"):
						return buf.Write([]byte(c.FormValue(tag[5:])))
					case strings.HasPrefix(tag, "cookie:"):
						cookie, err := c.Cookie(tag[7:])
						if err == nil {
							return buf.Write([]byte(cookie.Value))
						}
					}
				}

				return 0, nil
			}); err != nil {
				return
			}
			// ms, _ := json.Marshal(m)
			// ms = append(ms, []byte("\n")...)
			// _, err = config.Output.Write(ms)

			model.DB().Dbname = "OxyCms"
			model.DB().Document = "log"
			err = model.DB().Insert(m)
			if err != nil {
				log.Error("error, insert logger error")
			}
			return
		}
	}
}
